Introduction

In this workshop, we will explore how to create tables using the gt and gtExtras packages, utilizing the pizza place dataset. We will start with a basic table using base R to display pizza information, then enhance our visualization using the gt package to create a more polished and user-friendly table. Finally, we will upgrade our visualization further with the gtExtras package, adding even more features and styles to improve the overall presentation of our pizza menu.

The Data

Let’s look at our pizza sales dataset:

# Load and assign the pizza dataset to an object
data("pizzaplace")
pizza_data <- pizzaplace
head(pizza_data)
## # A tibble: 6 × 7
##   id          date       time     name        size  type    price
##   <chr>       <chr>      <chr>    <chr>       <chr> <chr>   <dbl>
## 1 2015-000001 2015-01-01 11:38:36 hawaiian    M     classic  13.2
## 2 2015-000002 2015-01-01 11:57:40 classic_dlx M     classic  16  
## 3 2015-000002 2015-01-01 11:57:40 mexicana    M     veggie   16  
## 4 2015-000002 2015-01-01 11:57:40 thai_ckn    L     chicken  20.8
## 5 2015-000002 2015-01-01 11:57:40 five_cheese L     veggie   18.5
## 6 2015-000002 2015-01-01 11:57:40 ital_supr   L     supreme  20.8

Basic Table (Before gt)

First, let’s create a summary table using basic R:

pizza_summary <- pizza_data %>%
  group_by(type, size) %>%
  summarize(
    total_sales = sum(price),
    num_orders = n(),
    avg_price = mean(price),
    .groups = 'drop'
  )

# Display as basic table
print(pizza_summary)
## # A tibble: 14 × 5
##    type    size  total_sales num_orders avg_price
##    <chr>   <chr>       <dbl>      <int>     <dbl>
##  1 chicken L         102339        4932      20.8
##  2 chicken M          65224.       3894      16.8
##  3 chicken S          28356        2224      12.8
##  4 classic L          74518.       4057      18.4
##  5 classic M          60582.       4112      14.7
##  6 classic S          69870.       6139      11.4
##  7 classic XL         14076         552      25.5
##  8 classic XXL         1007.         28      36.0
##  9 supreme L          94258.       4564      20.7
## 10 supreme M          66475        4046      16.4
## 11 supreme S          47464.       3377      14.1
## 12 veggie  L         104203.       5403      19.3
## 13 veggie  M          57101        3583      15.9
## 14 veggie  S          32387.       2663      12.2

Basic gt Features

Step 1: Basic gt Table

Let’s start with a simple gt table:

pizza_summary %>%
  gt(
    groupname_col = "type"
  )
size total_sales num_orders avg_price
chicken
L 102339.00 4932 20.75000
M 65224.50 3894 16.75000
S 28356.00 2224 12.75000
classic
L 74518.50 4057 18.36788
M 60581.75 4112 14.73292
S 69870.25 6139 11.38137
XL 14076.00 552 25.50000
XXL 1006.60 28 35.95000
supreme
L 94258.50 4564 20.65261
M 66475.00 4046 16.42981
S 47463.50 3377 14.05493
veggie
L 104202.70 5403 19.28608
M 57101.00 3583 15.93665
S 32386.75 2663 12.16175

Step 2: Adding Formatting

Now let’s add currency and number formatting:

pizza_summary %>%
  gt(
    groupname_col = "type"
  ) %>%
  fmt_currency(
    columns = c(total_sales, avg_price),
    currency = "USD"
  ) %>%
  fmt_number(
    columns = num_orders,
    decimals = 0
  )
size total_sales num_orders avg_price
chicken
L $102,339.00 4,932 $20.75
M $65,224.50 3,894 $16.75
S $28,356.00 2,224 $12.75
classic
L $74,518.50 4,057 $18.37
M $60,581.75 4,112 $14.73
S $69,870.25 6,139 $11.38
XL $14,076.00 552 $25.50
XXL $1,006.60 28 $35.95
supreme
L $94,258.50 4,564 $20.65
M $66,475.00 4,046 $16.43
S $47,463.50 3,377 $14.05
veggie
L $104,202.70 5,403 $19.29
M $57,101.00 3,583 $15.94
S $32,386.75 2,663 $12.16

Step 3: Adding Headers and Labels

Let’s improve the presentation with better headers:

pizza_summary %>%
  gt(
    groupname_col = "type"
  ) %>%
  fmt_currency(
    columns = c(total_sales, avg_price),
    currency = "USD"
  ) %>%
  fmt_number(
    columns = num_orders,
    decimals = 0
  ) %>%
  tab_header(
    title = "Pizza Sales Analysis",
    subtitle = "Sales breakdown by type and size"
  ) %>%
  cols_label(
    total_sales = "Total Sales",
    num_orders = "Number of Orders",
    avg_price = "Average Price",
    size = "Pizza Size"
  )
Pizza Sales Analysis
Sales breakdown by type and size
Pizza Size Total Sales Number of Orders Average Price
chicken
L $102,339.00 4,932 $20.75
M $65,224.50 3,894 $16.75
S $28,356.00 2,224 $12.75
classic
L $74,518.50 4,057 $18.37
M $60,581.75 4,112 $14.73
S $69,870.25 6,139 $11.38
XL $14,076.00 552 $25.50
XXL $1,006.60 28 $35.95
supreme
L $94,258.50 4,564 $20.65
M $66,475.00 4,046 $16.43
S $47,463.50 3,377 $14.05
veggie
L $104,202.70 5,403 $19.29
M $57,101.00 3,583 $15.94
S $32,386.75 2,663 $12.16

Step 4: Adding Visual Elements with gtExtras

Now let’s add some visual enhancements:

pizza_summary %>%
  gt(
    groupname_col = "type"
  ) %>%
  fmt_currency(
    columns = c(total_sales, avg_price),
    currency = "USD"
  ) %>%
  fmt_number(
    columns = num_orders,
    decimals = 0
  ) %>%
  tab_header(
    title = "Pizza Sales Analysis",
    subtitle = "Sales breakdown by type and size"
  ) %>%
  cols_label(
    total_sales = "Total Sales",
    num_orders = "Number of Orders",
    avg_price = "Average Price",
    size = "Pizza Size"
  ) %>%
  gt_theme_538() %>%
  data_color(
    columns = c(total_sales),
    colors = scales::col_numeric(
      palette = c("white", "#1F77B4"),
      domain = NULL
    )
  )
Pizza Sales Analysis
Sales breakdown by type and size
Pizza Size Total Sales Number of Orders Average Price
chicken
L $102,339.00 4,932 $20.75
M $65,224.50 3,894 $16.75
S $28,356.00 2,224 $12.75
classic
L $74,518.50 4,057 $18.37
M $60,581.75 4,112 $14.73
S $69,870.25 6,139 $11.38
XL $14,076.00 552 $25.50
XXL $1,006.60 28 $35.95
supreme
L $94,258.50 4,564 $20.65
M $66,475.00 4,046 $16.43
S $47,463.50 3,377 $14.05
veggie
L $104,202.70 5,403 $19.29
M $57,101.00 3,583 $15.94
S $32,386.75 2,663 $12.16

Advanced Features

Dark Theme Example

pizza_data %>%
  group_by(type) %>%
  summarize(
    total_sales = sum(price),
    num_orders = n(),
    avg_price = mean(price),
    sales_trend = list(tapply(price, date, mean))
  ) %>%
  gt() %>%
  
  # Make sure currency formatting is applied after theme
  gt_theme_dark() %>%
  
  # Format numbers
  fmt_currency(
    columns = c(total_sales, avg_price),
    currency = "USD",
    placement = "right",
    decimals = 2,
    use_subunits = TRUE
  ) %>%
  fmt_number(
    columns = num_orders,
    decimals = 0,
    use_seps = TRUE
  ) %>%
  
  # Sparkline and bar plots
  gt_plt_sparkline(sales_trend, label = FALSE) %>%
  
  # Remove bar plot for total_sales to ensure numbers are visible
  # gt_plt_bar(column = total_sales, color = "steelblue", width = 65) %>%
  
  # Header styling
  tab_header(
    title = md("🌙 Night Mode Sales Dashboard"),
    subtitle = md("*Visualization of pizza sales*")
  ) %>%
  
  # Column labels
  cols_label(
    type = md("**Pizza Type**"),
    total_sales = md("**Total Revenue**"),
    num_orders = md("**Orders**"),
    avg_price = md("**Avg. Price**"),
    sales_trend = md("**Sales Trend**")
  ) %>%
  
  # Column alignment
  cols_align(
    align = "left",
    columns = type
  ) %>%
  cols_align(
    align = "center",
    columns = c(num_orders, sales_trend)
  ) %>%
  cols_align(
    align = "right",
    columns = c(total_sales, avg_price)
  ) %>%
  
  # Column width adjustments
  cols_width(
    type ~ px(150),
    total_sales ~ px(180),
    num_orders ~ px(120),
    avg_price ~ px(120),
    sales_trend ~ px(150)
  ) %>%
  
  # Add borders and styling
  tab_style(
    style = list(
      cell_borders(
        sides = c("left", "right"),
        color = "#404040",
        weight = px(1)
      )
    ),
    locations = cells_body()
  ) %>%
  
  # Highlight highest values
  tab_style(
    style = list(
      cell_fill(color = "#2d4a54"),
      cell_text(weight = "bold", color = "white")  # Ensure text is visible
    ),
    locations = cells_body(
      columns = total_sales,
      rows = total_sales == max(total_sales)
    )
  ) %>%
  
  # Add row striping
  opt_row_striping() %>%
  
  # Table styling
  tab_options(
    table.background.color = "#1a1a1a",
    heading.title.font.size = px(28),
    heading.subtitle.font.size = px(18),
    heading.padding = px(20),
    column_labels.background.color = "#2d2d2d",
    column_labels.font.weight = "bold",
    column_labels.padding = px(15),
    table.border.top.style = "solid",
    table.border.top.width = px(3),
    table.border.top.color = "white",
    table.border.bottom.color = "white",
    table.border.bottom.width = px(3),
    column_labels.border.bottom.color = "white",
    column_labels.border.bottom.width = px(2),
    data_row.padding = px(12),
    row.striping.background_color = "#2a2a2a",
    table.font.color = "white",
    table.font.size = px(14)
  ) %>%
  
  # Add source note
  tab_source_note(
    source_note = md("*Data source: Pizza Place Sales Dataset*")
  )
🌙 Night Mode Sales Dashboard
Visualization of pizza sales
Pizza Type Total Revenue Orders Avg. Price Sales Trend
chicken 195,919.50$ 11,050 17.73$
classic 220,053.10$ 14,888 14.78$
supreme 208,197.00$ 11,987 17.37$
veggie 193,690.45$ 11,649 16.63$
Data source: Pizza Place Sales Dataset

Summary Rows Feature

pizza_data %>%
  group_by(type) %>%
  summarize(
    total_sales = sum(price),
    num_orders = n(),
    avg_price = mean(price)
  ) %>%
  ungroup() %>%
  gt(
    groupname_col = "type"
  ) %>%
  fmt_currency(
    columns = c(total_sales, avg_price),
    currency = "USD"
  ) %>%
  fmt_number(
    columns = num_orders,
    decimals = 0
  ) %>%
  grand_summary_rows(
    columns = total_sales,
    fns = list(
      "Total" = ~sum(.)
    ),
    fmt = ~ fmt_currency(., currency = "USD")  
  ) %>%
  grand_summary_rows(
    columns = num_orders,
    fns = list(
      "Total" = ~sum(.)
    ),
    fmt = ~ fmt_number(., decimals = 0)  
  )
total_sales num_orders avg_price
chicken
$195,919.50 11,050 $17.73
classic
$220,053.10 14,888 $14.78
supreme
$208,197.00 11,987 $17.37
veggie
$193,690.45 11,649 $16.63
Total $817,860.05 49,574

Final Demo

A Pizza Pricing Table Using the gt and gtExtras

# First create the pricing structure with features
pizza_pricing <- pizza_data %>%
  group_by(type, size) %>%
  summarize(
    price = mean(price),
    .groups = 'drop'
  ) %>%
  group_by(type) %>%
  summarize(
    basic = min(price),
    medium = median(price),
    large = max(price)
  ) %>%
  mutate(
    type_display = case_when(
      type == "chicken" ~ "🍗 Chicken Lovers",
      type == "classic" ~ "🍕 Classic Choice",
      type == "supreme" ~ "👑 Supreme Selection",
      type == "veggie" ~ "🥬 Veggie Delight"
    ),
    features = case_when(
      type == "chicken" ~ "✓ Fresh chicken ✓ Special herbs ✓ Premium cheese",
      type == "classic" ~ "✓ Traditional sauce ✓ Classic toppings ✓ Family favorite",
      type == "supreme" ~ "✓ Premium toppings ✓ Extra cheese ✓ Special blend",
      type == "veggie" ~ "✓ Fresh vegetables ✓ Light cheese ✓ Herb-infused"
    )
  )

# Create the enhanced pricing table
pizza_pricing %>%
  select(type_display, features, basic, medium, large) %>%
  gt() %>%
  tab_header(
    title = md("🌟 **Premium Pizza Price Plans** 🌟"),
    subtitle = md("*Explore our range of artisanal pizzas crafted just for you*")
  ) %>%
  
  cols_label(
    type_display = "",
    features = md("**Includes**"),
    basic = md("**Basic Plan**"),
    medium = md("**Premium Plan**"),
    large = md("**Deluxe Plan**")
  ) %>%
  
  fmt_currency(
    columns = c(basic, medium, large),
    currency = "USD"
  ) %>%
  
  # Style plan headers
  tab_style(
    style = list(
      cell_fill(color = "#4287f5"),
      cell_text(color = "white", weight = "bold", size = px(14)),
      cell_borders(sides = "all", color = "white", weight = px(2))
    ),
    locations = cells_column_labels(columns = basic)
  ) %>%
  
  tab_style(
    style = list(
      cell_fill(color = "#2ecc71"),
      cell_text(color = "white", weight = "bold", size = px(14)),
      cell_borders(sides = "all", color = "white", weight = px(2))
    ),
    locations = cells_column_labels(columns = medium)
  ) %>%
  
  tab_style(
    style = list(
      cell_fill(color = "#e74c3c"),
      cell_text(color = "white", weight = "bold", size = px(14)),
      cell_borders(sides = "all", color = "white", weight = px(2))
    ),
    locations = cells_column_labels(columns = large)
  ) %>%
  
  # Style pizza types
  tab_style(
    style = list(
      cell_text(weight = "bold", size = px(14)),
      cell_fill(color = "#f8f9fa"),
      cell_borders(sides = "all", color = "#dee2e6", weight = px(1))
    ),
    locations = cells_body(columns = type_display)
  ) %>%
  
  # Add borders
  tab_style(
    style = list(
      cell_borders(sides = "all", color = "#dee2e6", weight = px(2))
    ),
    locations = cells_body()
  ) %>%
  
  # Alignment
  cols_align(
    align = "center",
    columns = c(basic, medium, large)
  ) %>%
  
  cols_align(
    align = "left",
    columns = c(type_display, features)
  ) %>%
  
  # Column widths
  cols_width(
    type_display ~ px(180),
    features ~ px(250),
    everything() ~ px(150)
  ) %>%
  
  # Add plan features spanner
  tab_spanner(
    label = md("**🎯 Select Your Perfect Plan 🎯**"),
    columns = c(basic, medium, large)
  ) %>%
  
  # Style the prices
  tab_style(
    style = list(
      cell_text(weight = "bold", size = px(16)),
      cell_fill(color = "#ffffff")
    ),
    locations = cells_body(
      columns = c(basic, medium, large)
    )
  ) %>%
  
  # Add row striping
  opt_row_striping(row_striping = TRUE) %>%
  
  # Table styling
  tab_options(
    heading.background.color = "#f8f9fa",
    heading.title.font.size = px(28),
    heading.subtitle.font.size = px(16),
    heading.padding = px(20),
    column_labels.background.color = "#f8f9fa",
    table.border.top.width = px(3),
    table.border.bottom.width = px(3),
    table.border.top.color = "#4e73df",
    table.border.bottom.color = "#4e73df",
    data_row.padding = px(15),
    row.striping.background_color = "#f9f9f9",
    heading.title.font.weight = "bold"
  ) %>%
  
  # Add features styling
  tab_style(
    style = list(
      cell_borders(sides = "all", color = "#dee2e6", weight = px(1)),
      cell_fill(color = "#e8f4f8")
    ),
    locations = cells_body(columns = features)
  ) %>%
  
  # Add badges/footnotes
  tab_footnote(
    footnote = md("🔥 **Most Popular Choice**"),
    locations = cells_column_labels(columns = medium)
  ) %>%
  
  tab_footnote(
    footnote = md("⭐ **Best Value**"),
    locations = cells_column_labels(columns = large)
  ) %>%
  
  # Source notes
  tab_source_note(
    source_note = md(
      "✨ *All plans include our signature sauce and premium cheese*  |  
      🛵 *Free delivery on orders over $30*  |  
      💫 *Customization available for all plans*"
    )
  )
🌟 Premium Pizza Price Plans 🌟
Explore our range of artisanal pizzas crafted just for you
Includes
🎯 Select Your Perfect Plan 🎯
Basic Plan Premium Plan1 Deluxe Plan2
🍗 Chicken Lovers ✓ Fresh chicken ✓ Special herbs ✓ Premium cheese $12.75 $16.75 $20.75
🍕 Classic Choice ✓ Traditional sauce ✓ Classic toppings ✓ Family favorite $11.38 $18.37 $35.95
👑 Supreme Selection ✓ Premium toppings ✓ Extra cheese ✓ Special blend $14.05 $16.43 $20.65
🥬 Veggie Delight ✓ Fresh vegetables ✓ Light cheese ✓ Herb-infused $12.16 $15.94 $19.29
All plans include our signature sauce and premium cheese |
🛵 Free delivery on orders over $30 |
💫 Customization available for all plans
1 🔥 Most Popular Choice
2 Best Value